package ruru.server.backend;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import ruru.client.backend.RuruUtility;

public class Raid implements Comparable<Raid> {
	private Date start;
	private Date end;
	private Dungeon instance;

	private List<Raider> participants;
	private List<LootAssignment> assignments;
	private List<RaidPointException> exceptions;

	private int raidId;
	private double pointsAwarded;
	private String desc;

	private static final double MINBID = -10.0;
	// private static long serverTimeZoneOffset = 1000 * 60 * 60 * 2; //CST

	private static long serverTimeZoneOffset = 0; // maybe try doing it server side instead
	private static int counter = 0;

	public Raid(Dungeon instance, double pointsAwarded, String desc, Date start, Date end) {
		this.instance = instance;
		this.start = new Date(start.getTime());
		this.end = new Date(end.getTime());

		participants = new ArrayList<Raider>();
		assignments = new ArrayList<LootAssignment>();
		exceptions = new ArrayList<RaidPointException>();

		this.pointsAwarded = pointsAwarded;
		this.desc = desc;

		this.raidId = ++counter;
	}

	/**
	 * Adds a loot transaction to this raid =P
	 * 
	 * @throws IllegalArgumentException
	 *            if raider in assignment is not part of this raid.
	 */
	public void addLootAssignment(Raider raider, String lootName, Boss boss, String bidType) {
		if(!participants.contains(raider)) {
			throw new IllegalArgumentException("Raider " + raider + " is not part of this raid.  Loot: " + lootName);
		}

		BossDrop loot = new BossDrop(boss, lootName);
		LootAssignment assignment = new LootAssignment(raider, loot, bidType);

		assignments.add(assignment);
	}

	/**
	 * Updates maximum bids, taking exceptions into account. Updates raiders' last raid date. Updates items received by
	 * each raider. TODO: really shouldn't use strings for bid types and only check their validity here :(
	 */
	public void updateRaiderInfo() {
		// subtract points for loot received at minbid and halfbid
		for(int i = 0; i < assignments.size(); i++) {
			LootAssignment assignment = (LootAssignment) assignments.get(i);
			Raider raider = assignment.getRaider();

			double spent = 0;
			// double pointsBeforeSpending = raider.getPoints();
			if(assignment.getBidType().equals("min")) {
				spent = 10;
				raider.modifyPoints(MINBID);
				raider.gotItem();
			} else if(assignment.getBidType().equals("half")) {
				spent = raider.getPoints() / 2;
				// halves the raider's points
				raider.modifyPoints(raider.getPoints() / -2.0);
				raider.gotItem();
			} else if(assignment.getBidType().equals("fun")) {
				spent = 0;
				// do nothing because the item is free
			} else {
				throw new IllegalStateException("incorrect bidType format for a loot assignment: " + assignment.getBidType() + " from " + assignment.getRaider());
			}

			raider.addDkpChange("Item: " + RuruUtility.capitalize(RuruUtility.tokenizer(assignment.getBossDrop().getName())), -spent, start);
			assignment.setBidAmount(spent);
		}

		HashMap<String, Double> exceptionMap = new HashMap<String, Double>(); // raidername, adjustment
		// process exceptions (raider is late, raider leaves early)
		for(int i = 0; i < exceptions.size(); i++) {
			RaidPointException exception = (RaidPointException) exceptions.get(i);
			Raider raider = exception.getRaider();
			double adjustment = exception.getAdjustment(); // already negative

			if(!participants.contains(raider)) {
				throw new IllegalStateException("Raider " + raider.getName()
						+ " in RaidPointException object is not part of this raid");
			}

			exceptionMap.put(raider.getName(), new Double(adjustment));
		}

		// award dkp to each raider (note this happens after points are subtracted for loot won)
		for(int i = 0; i < participants.size(); i++) {
			Raider raider = (Raider) participants.get(i);
			double tmp;
			if(exceptionMap.containsKey(raider.getName())) {
				tmp = pointsAwarded + ((Double) exceptionMap.get(raider.getName())).doubleValue();
			} else {
				tmp = pointsAwarded;
			}
			
			raider.modifyPoints(tmp);
			raider.addDkpChange(instance + "", tmp, start);

			raider.setLastRaidDate(getStart());
			raider.incrementRaidCounter();
		}
				

	}

	public void addException(RaidPointException ex) {
		exceptions.add(ex);
	}

	/**
	 * Adds a raider to the raid. Raiders contains all registered raiders.
	 */
	public void addRaider(String name, HashMap<String, Raider> raiders) {
		if(!raiders.containsKey(name)) {
			throw new IllegalArgumentException(name + " is not a registered raider");
		}
		Raider person = (Raider) raiders.get(name);
		participants.add(person);
	}

	public String getDesc() {
		return desc;
	}

	public double getPointsAwarded() {
		return pointsAwarded;
	}

	/**
	 * Returns the list of raiders in this raid.
	 */
	public List<Raider> getParticipants() {
		return participants;
	}

	/**
	 * Returns the start Date.
	 */
	public Date getStart() {
		return new Date(start.getTime() + serverTimeZoneOffset);
	}

	/**
	 * Returns the end Date.
	 */
	public Date getEnd() {
		return new Date(end.getTime() + serverTimeZoneOffset);
	}

	public int getId() {
		return raidId;
	}

	public Dungeon getDungeon() {
		return instance;
	}

	/**
	 * Returns a List of LootAssignments (Raider, BossDrop pairs). BossDrops are (String lootname, Boss pairs)
	 * 
	 * @return
	 */
	public List<LootAssignment> getLootAssignments() {
		return assignments;
	}

	/**
	 * Returns a String representation of the Raid.
	 */
	public String toString() {
		return instance.toString();
	}

	public int compareTo(Raid arg0) {
		Raid target = arg0;
		return this.start.compareTo(target.start);
	}
}